home *** CD-ROM | disk | FTP | other *** search
- /*
- Copyright 2007-2008 Foxmarks Inc.
-
- foxmarks-rdf.js: implements class BookmarkDatasource, encapsulating
- the Firefox RDF datasource.
-
- */
-
- // To do:
- // * icons in 1.0.1: whoops!
- // * fix (ahem) exception text
- // * figure out how to move helper mapping functions back into prototype
- // * Never set a date backward in time.
-
-
- var Cc = Components.classes;
- var Ci = Components.interfaces;
- const RDF_ROOT = "NC:BookmarksRoot";
- const NC = "http://home.netscape.com/NC-rdf#";
- const NS = "http://home.netscape.com/WEB-rdf#";
- const RDF = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
-
- function ParseIconString(s) {
- var exp = /^data:(.*);base64,(.*)$/;
- var match = exp.exec(s);
-
- if (match) {
- try {
- var data = My_atob(match[2]);
- } catch (e) {
- return null;
- }
- return [match[1] /* mime type */, data /* b64-encoded data */];
- } else {
- return null;
- }
- }
-
-
- // Node's representation of dates is seconds since 1/1/1970.
- // RDF's representation of dates is microseconds since 1/1/1970.
- // Use these utility functions to convert between the two formats
- // while minimizing rounding error.
-
- function DateRDFToNode(v) {
- return Math.round(v / 1000000);
- }
-
- function DateNodeToRDF(v) {
- return v * 1000000;
- }
-
- const MAP_RDF_TYPE = {
- "http://home.netscape.com/NC-rdf#Bookmark" : "bookmark",
- "http://home.netscape.com/NC-rdf#Folder" : "folder",
- "http://home.netscape.com/NC-rdf#BookmarkSeparator" : "separator",
- "http://home.netscape.com/NC-rdf#Livemark" : "feed",
- "http://home.netscape.com/NC-rdf#MicsumBookmark" : "microsummary"
- }
-
- const MAP_NODE_TYPE = {
- "bookmark" : null, // special case: bookmarks & folders
- "folder" : null, // have their types inferred
- "separator" : NC + "BookmarkSeparator",
- "feed" : NC + "Livemark",
- "microsummary" : NC + "MicsumBookmark"
- };
-
- // Node type requires special mapping
- function MapRDFType(node, resource, predicate, target) {
- if (target instanceof Ci.nsIRDFResource) {
- node.ntype = MAP_RDF_TYPE[target.Value];
- } else {
- throw Error("Whatever! I do what I want!");
- }
- };
-
-
- // Returns either:
- // 1) array of [pred, targ, targtype (literal vs. resource)]
- // 2) null (skip it)
- // 3) string (reset incoming attribute to this type and skip assertion)
-
- function MapNodeType(ntype) {
- if (ntype in MAP_NODE_TYPE) {
- var map = MAP_NODE_TYPE[ntype];
- if (map) {
- return [RDF + "type", map, true];
- } else {
- return null;
- }
- } else {
- return "bookmark";
- }
- }
-
- // Sidebar requires special mapping, as it's stored in RDF as a literal
- // but we want to present as boolean in our node structure.
- function MapRDFSidebar(node, resource, predicate, target) {
- node.sidebar = true;
- }
-
- function MapNodeSidebar(sidebar) {
- if (sidebar) {
- return [NC + "WebPanel", "true", false];
- } else {
- return [null, null, false];
- }
- }
-
- function BookmarkDatasource() {
- this.rdfs = Cc["@mozilla.org/rdf/rdf-service;1"].getService(
- Ci.nsIRDFService);
-
- this.ds = this.rdfs.GetDataSource("rdf:bookmarks").
- QueryInterface(Ci.nsIBookmarksService);
-
- this.container = Cc["@mozilla.org/rdf/container;1"].
- createInstance(Ci.nsIRDFContainer);
- this.rdfcu = Cc["@mozilla.org/rdf/container-utils;1"].
- getService(Ci.nsIRDFContainerUtils);
- this.lastModifiedPredicate = this.rdfs.
- GetResource("http://home.netscape.com/WEB-rdf#LastModifiedDate");
- this.addDatePredicate = this.rdfs.
- GetResource("http://home.netscape.com/NC-rdf#BookmarkAddDate");
- this.typePredicate = this.rdfs.
- GetResource("http://www.w3.org/1999/02/22-rdf-syntax-ns#type");
- if (!BookmarkDatasource._mapNidToNative) {
- this._InitNidNativeMaps();
- }
-
- applyCommonBookmarkFunctions(this);
-
- }
-
- // For backwards compatibility, this must be an empty string
- BookmarkDatasource.STORAGE_ENGINE = "";
-
- BookmarkDatasource.startupTime = Date.now()
-
- BookmarkDatasource.ProvideNodesDone = function(status) {
- var tb = this.GetToolbar();
- if (tb) {
- this.pn.Caller.Node(NODE_ROOT, true).tnid = tb;
- }
- this.pn.Complete.apply(this.pn.Caller, [status]);
- this.pn.Caller.rdfSource = true;
- this.pn = null;
- return;
- }
-
- BookmarkDatasource.MapRDFToNode = function(resource, pnid) {
- var nid = this.MapNative(resource.Value);
- var node = new Node(nid);
- var predicates = this.ds.ArcLabelsOut(resource);
- node.pnid = pnid;
-
- while (predicates.hasMoreElements()) {
- var predicate = predicates.getNext();
-
- if (this.rdfcu.IsOrdinalProperty(predicate))
- continue;
-
- var target = this.ds.GetTarget(resource, predicate, true);
-
- // sometimes, targets don't exist (icons in particular)
- // just skip 'em
- if (!target)
- continue;
-
- predicate.QueryInterface(Ci.nsIRDFResource);
- target.QueryInterface(Ci.nsIRDFNode);
-
- var map = this.RDF_NODE_MAP[predicate.Value];
- var value = null;
-
- if (map === null) {
- // do nothing
- } else if (typeof(map) == "string") {
- if (target instanceof Ci.nsIRDFResource) {
- value = String(target.Value);
- } else if (target instanceof Ci.nsIRDFLiteral) {
- value = String(target.Value);
- } else if (target instanceof Ci.nsIRDFDate) {
- value = parseInt(target.Value / 1000000.0 + 0.5);
- } else {
- throw "Erggh! unknown target " + predicate.Value +
- "/" + target.Value;
- }
- node[map] = value;
- } else if (typeof(map) == "function") {
- map(node, resource, predicate, target);
- } else {
- throw Error(
- "Damn, woman! You trying to kill me or something?" +
- predicate.Value + " / " + typeof(map) +
- " / " + map);
- }
- }
-
- if (this.IsContainer(resource)) {
- node.ntype = "folder";
- node.children = [];
- this.container.Init(this.ds, resource);
- var children = this.container.GetElements();
- while (children.hasMoreElements()) {
- var child = children.getNext();
- if (child instanceof Ci.nsIRDFResource)
- node.children.push(this.MapNative(String(child.Value)));
- }
- }
- if (Xmarks.gSettings.disableIconSync) {
- delete node["icon"];
- }
- this.pn.AddNode.apply(this.pn.Caller, [node]);
- return 0;
- }
-
- // ot is the static object that maintains state for OnTree,
- // the tree-walker.
-
- var ot = {}
- BookmarkDatasource.Continue = {
- notify: function(timer) {
- var self = ot.self;
- var resources = ot.resources;
- var result;
- var s = Date.now();
- while (resources.length > 0 && Date.now() - s < 100) {
- var next = resources.shift();
- var resource = next[0].QueryInterface(Ci.nsIRDFResource);
- var pnid = next[1];
-
- try {
- result = ot.action.apply(ot.Caller, [resource, pnid]);
- } catch (e) {
- Xmarks.LogWrite("OnTree error " + e.toSource());
- result = 3;
- }
-
- if (result)
- break;
-
- if (self.IsContainer(resource)) {
- self.container.Init(self.ds, resource);
-
- var children = self.container.GetElements();
- var ix = 0;
- var nid = self.MapNative(resource.Value);
- while (children.hasMoreElements()) {
- if (ot.depthfirst) {
- resources.splice(ix, 0,
- [children.getNext(), nid]);
- ix += 1;
- } else {
- resources.push(
- [children.getNext(), nid]);
- }
- }
- }
- }
-
- if (resources.length > 0 && !result) {
- ot.timer.initWithCallback(BookmarkDatasource.Continue, 10,
- Ci.nsITimer.TYPE_ONE_SHOT);
- } else {
- ot.complete.apply(ot.Caller, [result]);
- }
- }
- }
-
- BookmarkDatasource.prototype = {
-
- _InitNidNativeMaps: function() {
- BookmarkDatasource._mapNidToNative = {};
- BookmarkDatasource._mapNativeToNid = {};
- this.AddToMap(RDF_ROOT, NODE_ROOT);
- },
-
- MapNid: function(nid) {
- return BookmarkDatasource._mapNidToNative[nid] || "rdf:#$" + nid;
- },
-
- MapNative: function(resourceId) {
- return BookmarkDatasource._mapNativeToNid[resourceId] ||
- (resourceId.slice(0, 6) == "rdf:#$" ?
- resourceId.slice(6) : this.AddToMap(resourceId, resourceId));
- },
-
- AddToMap: function(resourceId, nid) {
- BookmarkDatasource._mapNidToNative[nid] = resourceId;
- BookmarkDatasource._mapNativeToNid[resourceId] = nid;
- return nid;
- },
-
- BaselineLoaded: function(baseline, callback) {
- var self = this;
- self._InitNidNativeMaps();
- self.OnTree(this, ScanForSeparators, Done);
- return;
-
- function ScanForSeparators(resource, pnid) {
- if (!IsSeparator(resource)) {
- return 0;
- }
-
- var parent = baseline.Node(pnid, false, true);
- if (!parent) {
- return 0;
- }
-
- for (var i = 0; i < parent.children.length; ++i) {
- var child = baseline.Node(parent.children[i]);
- if (child.ntype == "separator" &&
- !BookmarkDatasource._mapNidToNative[child.nid]) {
- self.AddToMap(String(resource.Value), child.nid);
- break;
- }
- }
- return 0;
- }
-
- function Done(status) {
- callback(status);
- }
-
- function IsSeparator(resource) {
- return (self._GetType(resource) ==
- "http://home.netscape.com/NC-rdf#BookmarkSeparator");
- }
- },
-
- MAP_NODE_RDF: {
- "nid" : null,
- "pnid" : null,
- "children" : null,
- "tnid" : null,
- "ntype" : MapNodeType,
- "description" : [NC + "Description", Ci.nsIRDFLiteral],
- "name" : [NC + "Name", Ci.nsIRDFLiteral],
- "created" : [NC + "BookmarkAddDate", Ci.nsIRDFDate],
- "url" : [NC + "URL", Ci.nsIRDFLiteral],
- "feedurl" : [NC + "FeedURL", Ci.nsIRDFLiteral],
- "sidebar" : MapNodeSidebar,
- "shortcuturl" : [NC + "ShortcutURL", Ci.nsIRDFLiteral],
- "generateduri" : [NC + "MicsumGenURI", Ci.nsIRDFLiteral],
- "generatedtitle": null,
- "contenttype" : [NC + "ContentType", Ci.nsIRDFLiteral],
- "msexpires" : null,
- "icon" : [NC + "Icon", Ci.nsIRDFLiteral],
- "formdata" : [NC + "PostData", Ci.nsIRDFLiteral],
- "modified" : null,
- "visited" : [NS + "LastVisitDate", Ci.nsIRDFDate]
- },
-
-
- IMMUTABLE_RDF: {
- "http://home.netscape.com/WEB-rdf#LastModifiedDate" : true,
- "http://home.netscape.com/NC-rdf#BookmarksToolbarFolder": true,
- "http://home.netscape.com/NC-rdf#ID" : true,
- "http://developer.mozilla.org/rdf/vocabulary/forward-proxy#forward-proxy"
- : true,
- "http://home.netscape.com/NC-rdf#LivemarkExpiration" : true,
- "http://home.netscape.com/WEB-rdf#LastCharset" : true,
- "http://home.netscape.com/NC-rdf#Icon" : true,
- "http://home.netscape.com/NC-rdf#GeneratedTitle" : true,
- "http://home.netscape.com/NC-rdf#MicsumExpiration" : true
- },
-
- AcceptNodes: function(ns, callback) {
- var self = this;
- var optimizeOK = ns._cloneSource && ns._cloneSource.rdfSource;
- self.StartWrite();
- ns.OnTree(FlushNode, Done);
- return;
-
- function FlushNode(nid, pnid) {
- if (optimizeOK && !ns._node[nid]) {
- // We can skip writing this because we know that the
- // nodeset in question is cloned from a nodeset that
- // originated with the local datastore, and the node
- // in question exists only in the clone source; it's
- // thus unchanged.
- return 0;
- }
-
- self.WriteNode(ns, ns.Node(nid));
- return 0;
- }
-
- function Done(status) {
- if (status) {
- callback(status);
- } else {
- // You might think that we should set the toolbar
- // before calling EndWrite, but (at least in the case
- // of Firefox 2), you'd be wrong: due to a repaint bug,
- // the toolbar ignores changes to the designated toolbar
- // while it's in the middle of batch updates. One could
- // argue that this should be handled in SetToolbar().
- self.EndWrite();
- self.SetToolbar(ns.Node(NODE_ROOT).tnid);
- callback(0);
- }
- }
- },
-
- // Call StartWrite before calling WriteNode
- StartWrite: function() {
- this.ds.beginUpdateBatch();
- },
-
- // Call EndWrite after we're done calling WriteNode
- EndWrite: function() {
- this.ds.endUpdateBatch();
- },
-
- WriteNode: function(ns, node) {
- // iterate over node's properties; generate list of
- // prospective assertions
-
- if (node == null) {
- throw Error("node is null");
- }
-
- var assert = [];
- var attrs = node.GetSafeAttrs();
-
- for (var attr in attrs) {
-
- if (!attrs.hasOwnProperty(attr)) {
- continue;
- }
-
- var map = this.MAP_NODE_RDF[attr];
- var targ = null;
- var pred = null;
- var value = attrs[attr];
-
- if (map === null) {
- continue; // do nothing
- } else if (typeof map == "object") {
- // XXX: Optimization: build these in advance
- pred = this.rdfs.GetResource(map[0]);
- var targType = map[1];
-
- if (targType == Ci.nsIRDFLiteral) {
- targ = this.rdfs.GetLiteral(value);
- } else if (targType == Ci.nsIRDFDate) {
- targ = this.rdfs.GetDateLiteral(DateNodeToRDF(value));
- } else {
- throw Error("Knuckle up! " + typeof value);
- }
- } else if (typeof map == "function") {
- var a = map(value);
- if (!a) {
- continue;
- } else if (typeof a == 'object') {
- pred = this.rdfs.GetResource(a[0]);
- targ = a[2] ? this.rdfs.GetResource(a[1]) :
- this.rdfs.GetLiteral(a[1]);
- } else if (typeof a == "string") {
- ns.Node(node.nid, true)[attr] = a;
- Xmarks.LogWrite("Warning: Set " + attr + " for node " +
- node.nid + " to " + a);
- continue;
- }
- } else {
- delete ns.Node(node.nid, true)[attr];
- Xmarks.LogWrite("Warning: dropping unrecognized attribute " + attr);
- continue;
- }
-
- assert.push([pred, targ]);
- }
-
- var subj = this.rdfs.GetResource(this.MapNid(node.nid));
-
- if (node.ntype == 'folder') {
- // Are we a folder with no children?
- if (node["children"] == null) {
- node.children = [];
- }
- for (var i = 0; i < node.children.length; ++i) {
- var pred = this.rdfcu.IndexToOrdinalResource(i + 1);
- var targ = this.rdfs.GetResource(this.MapNid(node.children[i]));
- assert.push([pred, targ]);
- }
- assert.push([this.rdfs.GetResource(RDF + "nextVal"),
- this.rdfs.GetLiteral("" + (1 + node.children.length))]);
- // Since we are not asserting the type of bookmark and folder
- // nodes, we assert instanceof Seq so that Firefox can infer
- // that we're talking about a folder here.
- assert.push([this.rdfs.GetResource(RDF + "instanceOf"),
- this.rdfs.GetResource(RDF + "Seq")]);
- } else if (node.ntype == 'feed') {
- // This is some code to keep from deleting children of a
- // Livemark
- var nextVal = this.ds.GetTarget(subj,
- this.rdfs.GetResource(RDF + "nextVal"), true);
- if (nextVal) {
- assert.push([this.rdfs.GetResource(RDF + "nextVal"), nextVal]);
- }
- assert.push([this.rdfs.GetResource(RDF + "instanceOf"),
- this.rdfs.GetResource(RDF + "Seq")]);
- }
-
- // Iterate over existing assertions. For each assertion we find,
- // either:
- // (1) delete it entirely
- // (2) change it to match the new target value
- // (3) do nothing, because it is perfect as it is
- // In either of the latter two cases, remove the match from
- // the prospective list.
-
- var alo = this.ds.ArcLabelsOut(subj);
-
- while (alo.hasMoreElements()) {
- var pred = alo.getNext();
- var targ = this.ds.GetTarget(subj, pred, true);
-
- var i = FindPredicate(pred);
- if (i < 0) {
- // No match; delete it.
- if (pred instanceof Ci.nsIRDFResource) {
- // Don't Unassert if:
- // 1) It's immutable, or
- // 2) It's a Livemark's ordinal property
- if (this.IMMUTABLE_RDF[pred.Value] == null &&
- (node.ntype != 'feed' ||
- !this.rdfcu.IsOrdinalProperty(pred)) ) {
- Xmarks.LogWrite("Unassert("+subj.Value+","+pred.Value+")");
- this.ds.Unassert(subj, pred, targ, true);
- } else if (pred.Value == NC + "Icon" &&
- !Xmarks.gSettings.disableIconSync) {
- try {
- this.ds.removeBookmarkIcon(node.url);
- } catch (e) {
- Xmarks.LogWrite("removeBookmarkIcon: " + e.toSource());
- }
- }
- } else {
- throw Error("Awwww, man!");
- }
- } else {
- // We matched the predicate. Do we need to change
- // the target?
- if (!TargetsMatch(targ, assert[i][1])) {
- if (this.IMMUTABLE_RDF[pred.Value] == null) {
- Xmarks.LogWrite("Change("+subj.Value+","+pred.Value+")");
- this.ds.Change(subj, pred, targ, assert[i][1]);
- assert.splice(i, 1);
- }
- } else {
- assert.splice(i, 1);
- }
- }
- }
-
- // execute remaining assertions against datastore
- for (var i = 0; i < assert.length; ++i) {
- var a = assert[i];
- a[0].QueryInterface(Ci.nsIRDFResource);
- a[1].QueryInterface(Ci.nsIRDFNode);
- Xmarks.LogWrite("Asserting " + subj.Value + "," +
- a[0].Value + "," + a[1].Value);
- if (a[0].Value == NC + "Icon" &&
- this.ds instanceof Ci.nsIBookmarksService) {
- var iconPieces = ParseIconString(a[1].Value);
- if (iconPieces) {
- this.ds.updateBookmarkIcon(node["url"],
- iconPieces[0], iconPieces[1], iconPieces[1].length);
- }
- } else {
- this.ds.Assert(subj, a[0], a[1], true);
- }
- }
-
- function FindPredicate(p) {
- for (i = 0; i < assert.length; ++i) {
- if (assert[i][0] == p)
- return i;
- }
- return -1;
- }
-
- function TargetsMatch(t1, t2) {
- if (t1 instanceof Ci.nsIRDFDate) {
- return (DateRDFToNode(t1.Value) == DateRDFToNode(t2.Value));
- } else {
- return t1 == t2;
- }
- }
- },
-
- SetToolbar: function(nid) {
- if (this.GetToolbar() != nid) {
- Xmarks.LogWrite("Setting toolbar nid to " + nid);
- this.ds.setBookmarksToolbarFolder(
- this.rdfs.GetResource(this.MapNid(nid)));
- }
- },
-
- GetToolbar: function() {
- if (this.ds instanceof Ci.nsIBookmarksService) {
- var tb = this.ds.getBookmarksToolbarFolder();
- return tb ? this.MapNative(tb.Value) : null;
- } else {
- throw "Criminy! It's not an nsIBookmarksService";
- }
- },
-
- // traverses this's bookmarks hierarchy starting with
- // startnode, calling action(node) for each node in the tree,
- // then calling complete() when traversal is done.
- // enforces rules about maximum run times to prevent hanging the UI
- // when traversing large trees or when running on slow CPU's.
- // action() should return 0 to continue, non-zero status to abort.
- // complete() is called with non-zero status if aborted.
- // depthfirst determines tree traversal order
-
-
- OnTree: function(Caller, action, complete, startnode, depthfirst) {
- ot = {}
- ot.self = this;
- ot.Caller = Caller;
- ot.action = action;
- ot.complete = complete;
- ot.startnode = startnode || this.rdfs.GetResource(RDF_ROOT);
- ot.depthfirst = depthfirst;
- ot.resources = [[ot.startnode, null]];
- ot.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
- ot.timer.initWithCallback(BookmarkDatasource.Continue,
- 10, Ci.nsITimer.TYPE_ONE_SHOT);
- return;
- },
-
- IsContainer: function(resource)
- {
- if (!this.rdfcu.IsContainer(this.ds, resource))
- return false;
-
- // if it's a Livemark, don't consider it a container, as we don't
- // process its children
- var type = this.ds.GetTarget(resource, this.typePredicate, true);
-
- return (type instanceof Ci.nsIRDFResource &&
- type.Value != "http://home.netscape.com/NC-rdf#Livemark");
- },
-
- RDF_NODE_MAP: {
- "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" : MapRDFType,
- "http://home.netscape.com/NC-rdf#Description" : "description",
- "http://home.netscape.com/NC-rdf#Name" : "name",
- "http://home.netscape.com/NC-rdf#BookmarkAddDate" : "created",
- "http://home.netscape.com/NC-rdf#URL" : "url",
- "http://home.netscape.com/NC-rdf#FeedURL" : "feedurl",
- "http://home.netscape.com/NC-rdf#WebPanel" : MapRDFSidebar,
- "http://home.netscape.com/NC-rdf#ShortcutURL" : "shortcuturl",
- "http://home.netscape.com/NC-rdf#LivemarkExpiration": null,
- "http://home.netscape.com/NC-rdf#Icon" : "icon",
- "http://home.netscape.com/NC-rdf#BookmarksToolbarFolder"
- : null,
- "http://home.netscape.com/WEB-rdf#LastModifiedDate" : "modified",
- "http://home.netscape.com/WEB-rdf#LastVisitDate" : "visited",
- "http://home.netscape.com/NC-rdf#MicsumGenURI" : "generateduri",
- "http://home.netscape.com/NC-rdf#GeneratedTitle" : null,
- "http://home.netscape.com/NC-rdf#ContentType" : "contenttype",
- "http://home.netscape.com/NC-rdf#MicsumExpiration" : null,
- "http://home.netscape.com/NC-rdf#LivemarkLock" : null,
- "http://home.netscape.com/NC-rdf#PostData" : "formdata",
- "http://www.w3.org/1999/02/22-rdf-syntax-ns#children"
- : null,
- "http://www.w3.org/1999/02/22-rdf-syntax-ns#parent" : null,
- "http://www.w3.org/1999/02/22-rdf-syntax-ns#instanceOf"
- : null,
- "http://www.w3.org/1999/02/22-rdf-syntax-ns#nextVal"
- : null,
- "http://home.netscape.com/NC-rdf#ID" : null,
- "http://home.netscape.com/WEB-rdf#LastCharset" : null,
- "http://developer.mozilla.org/rdf/vocabulary/forward-proxy#forward-proxy"
- : null
- },
-
- GenerateNid: function() {
- var resource = this.rdfs.GetAnonymousResource();
- if (resource instanceof Ci.nsIRDFResource) {
- return this.MapNative(resource.Value);
- } else {
- throw Error("Dagnabbit! Couldn't create anonymous resource");
- }
- },
-
- NormalizeUrl: function(url) {
- return url;
- },
-
- ProvideNodes: function(Caller, AddNode, Complete) {
- this.pn = {}
- this.pn.Caller = Caller;
- this.pn.AddNode = AddNode;
- this.pn.Complete = Complete;
- this.OnTree(this, BookmarkDatasource.MapRDFToNode,
- BookmarkDatasource.ProvideNodesDone);
- return;
- },
-
- WatchForChanges: function() {
- var watcher = new BookmarkWatcher();
- // start observing
- this.ds.AddObserver(watcher);
- return watcher;
- },
- _GetType: function(resource) {
- var type = this.ds.GetTarget(resource, this.typePredicate, true)
- if (type instanceof Ci.nsIRDFResource) {
- return type.Value;
- } else {
- return null;
- }
- }
- };
-
- function BookmarkWatcher(){
- this.rdfs = Cc["@mozilla.org/rdf/rdf-service;1"].getService(
- Ci.nsIRDFService);
- this.ds = this.rdfs.GetDataSource("rdf:bookmarks").
- QueryInterface(Ci.nsIBookmarksService);
- this.typePredicate = this.rdfs.
- GetResource("http://www.w3.org/1999/02/22-rdf-syntax-ns#type");
- }
-
- BookmarkWatcher.prototype = {
- lastModified: null,
-
- NotifyObservers: function(lm) {
- // Take in RDF's microseconds since 1970.
- // Output Javascript milliseconds since 1970.
- lm = Math.round(lm / 1000);
- if (!this.lastModified || lm > this.lastModified) {
- this.lastModified = lm;
- var os = Cc["@mozilla.org/observer-service;1"]
- .getService(Ci.nsIObserverService);
- os.notifyObservers(null, "foxmarks-datasourcechanged",
- lm + ";bookmarks");
- }
- },
-
- _GetType: function(resource) {
- var type = this.ds.GetTarget(resource, this.typePredicate, true)
- if (type instanceof Ci.nsIRDFResource) {
- return type.Value;
- } else {
- return null;
- }
- },
-
-
- ////////////////////////////////////////////////////////////////////////////
- //
- // nsIRDFObserver
-
- onAssert: function(dataSource, source, property, target) {
- },
-
- onUnassert: function(dataSource, source, property, target) {
- },
-
- onChange: function(dataSource, source, property, oldTarget, newTarget) {
- var self = this;
-
- var funcIsLivemark = function(resource) {
- return (self._GetType(resource) ==
- "http://home.netscape.com/NC-rdf#Livemark");
- };
- var funcIsMicsum = function(resource){
- return (self._GetType(resource) ==
- "http://home.netscape.com/NC-rdf#MicsumBookmark");
- }
-
- if (property.QueryInterface(Components.interfaces.nsIRDFResource).Value
- == "http://home.netscape.com/WEB-rdf#LastModifiedDate" &&
- !funcIsLivemark(source) && !funcIsMicsum(source)) {
- self.NotifyObservers(newTarget.
- QueryInterface(Ci.nsIRDFDate).Value);
- }
- },
-
- onBeginUpdateBatch: function(dataSource) {
- },
-
-
- onEndUpdateBatch: function(dataSource) {
- // Some kind of changes happened. Alas, the bookmark manager
- // has hidden from us exactly what happened. To be safe,
- // assume that something has changed and fire a sync as required.
- // Oh, and ignore notifications in the first 10 seconds after startup.
- if (Date.now() - BookmarkDatasource.startupTime >= 10000) {
- if (!Xmarks.gSettings.disableDirtyOnBatch) {
- Xmarks.LogWrite("onEndUpdateBatch()");
- this.NotifyObservers(Date.now() * 1000);
- } else {
- Xmarks.LogWrite("ignoring onEndUpdateBatch");
- }
- }
- }
- };
-
-